﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;

namespace CodeTalk
{
    public enum SymbolCategory
    {
        /// <summary>
        /// EX: int myLocal
        /// </summary>
        Local,

        /// <summary>
        /// EX: public int MyVar
        /// </summary>
        Open,

        /// <summary>
        /// EX: int _MyVar
        /// </summary>
        Hidden, 
        
        /// <summary>
        /// EX: int MyFunction()
        /// </summary>
        Function,


        /// <summary>
        /// EX: class MyType
        /// </summary>
        Type,

        /// <summary>
        /// EX: namespace MyNamespace
        /// </summary>
        Namespace,

        nSymbolCategories
    }

    public class CodeSymbol
    {
        public string WrittenForm { get; set; }
        public string SpokenForm { get; set; }
        public SymbolCategory Category { get; set; }

        public string CategoryString
        {
            get { return Category.ToString(); }
            set
            {
                for (int i = 0; i < (int)SymbolCategory.nSymbolCategories; i++)
                {
                    if (value == ((SymbolCategory)i).ToString())
                    {
                        Category = (SymbolCategory)i;
                        return;
                    }
                }

                throw new ArgumentException();
            }
        }

        /// <summary>
        /// Determines whether this symbol should be saved or not. 
        /// </summary>
        public bool Permanent { get; set; }

        /// <summary>
        /// If the symbol is a Type, this variable determines the number of generic arguments it has. 
        /// If the symbol is a Function, this variable determines the number of 
        /// parameters the function will take, if it is known. If unknown it will be set to 1.
        /// 
        /// If neither of the above conditions are met, this variable should be ignored. 
        /// </summary>
        public int Parameters { get; set; }

        /// <summary>
        /// If the symbol is a Type, this variable determines the written form of the Type this Type is declared within, if any. 
        /// If the symbol is an identifier, this variable determines the Type that has this identifier as member, if any. 
        /// In other words, if you can write X.Y where Y is this symbol and X is a Type or a Namespace, then X is the ParentType.
        /// The parent type must be in the same context as this symbol.
        /// </summary>
        public string ParentType { get; set; }

        /// <summary>
        /// 
        /// </summary>
        public string File { get; set; }

        public string Solution { get; set; }

        /// <summary>
        /// A number uniquely identifying this symbol across contexts. Two symbols of different contexts cannot have the same ID. 
        /// </summary>
        public uint SymbolID { get; private set; }

        public CodeSymbol(string WrittenForm, string SpokenForm, SymbolCategory Category, uint SymbolID)
        {
            this.WrittenForm = WrittenForm;
            this.SpokenForm = SpokenForm.ToLowerInvariant();
            this.Category = Category;
            this.Permanent = true;
            if (Category == SymbolCategory.Function)
                this.Parameters = 1;
            else
                this.Parameters = 0;
            this.ParentType = "";
            this.File = "";
            this.Solution = "";
            this.SymbolID = SymbolID;
        }

        /// <summary>
        /// Determines the category of a symbol. 
        /// </summary>
        /// <param name="s">A string representing the symbol. </param>
        /// <returns>The category of the symbol if it is a valid symbol; null if not. </returns>
        internal static SymbolCategory? DetermineCategory(string s)
        {
            if (s.Length < 1) return null;

            if (!char.IsLetter(s[0]) && s[0] != '_')
                return null;

            for (int i = 0; i < s.Length; i++)
            {
                if (!char.IsLetter(s[i]) && !char.IsDigit(s[i]) && s[i] != '_')
                {
                    if (s[i] == '(')
                    {
                        if (i == s.Length - 1) return SymbolCategory.Function;
                        if (i == s.Length - 2 && s[i + 1] == ')') return SymbolCategory.Function;
                        return null;
                    }
                    else
                    {
                        return null;
                    }
                }
            }

            if (s[0] == '_') return SymbolCategory.Hidden;

            if (char.IsUpper(s[0]))
                return SymbolCategory.Open;
            else
                return SymbolCategory.Local;
        }

        //public abstract string Serialize();
        //public abstract bool Deserialize(string line);

        internal string Serialize()
        {
            return WrittenForm + "€" + SpokenForm + "€" + (int)Category + "€" + Permanent + "€" +
                Parameters + "€" + ParentType + "€" + File + "€" + Solution + "€" + SymbolID;
        }

        internal static CodeSymbol Deserialize(string line)
        {
            try 
	        {	        
                string[] tokens = line.Split('€');
                if (tokens.Length != 9)
                    return null;
                CodeSymbol cs = new CodeSymbol(tokens[0], tokens[1], (SymbolCategory)int.Parse(tokens[2]), uint.Parse(tokens[8]));
                cs.Permanent = bool.Parse(tokens[3]);
                cs.Parameters = int.Parse(tokens[4]);
                cs.ParentType = tokens[5];
                cs.File = tokens[6];
                cs.Solution = tokens[7];
                return cs;
	        }
	        catch (Exception)
	        {
                return null;
	        }
        }
    }
}
